package api

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

const kHttpReadTimeout = time.Second * 10
const kHttpWriteTImeout = time.Second * 10

type Server struct {
	http         *http.Server
	eventHandler ServerHandler
}

type ServerHandler interface {
	OnStatusReq() *Response
	OnCallReq(*Request) *Response
	OnAnswerReq(*Request) *Response
	OnTerminateReq(*Request) *Response
	OnCandidateReq(*Request) *Response
	OnSubscribeReq(*Request) *Response
	OnUnSubscribeReq(*Request) *Response
	OnPubRemote(*Request) *Response
	OnUnPubRemote(*Request) *Response
	OnBootReq(*Request) *Response
	OnNewScene(*Request) *Response
	OnDelScene(*Request) *Response
	OnNewSegment(*Request) *Response
	OnNewSegment_2(*Request) *Response
	OnDelSegment(*Request) *Response
	OnDelSegment_2(*Request) *Response
	OnRtcUpd(*Request) *Response
}

func NewServer(addr string) *Server {
	server := &Server{}
	mux := http.NewServeMux()

	mux.HandleFunc("/", server.handleRoot)
	mux.HandleFunc("/rtc", server.handleRtc)
	mux.HandleFunc("/rtc/", server.handleRtc)

	server.http = &http.Server{
		Addr:         addr,
		ReadTimeout:  kHttpReadTimeout,
		WriteTimeout: kHttpWriteTImeout,
		Handler:      mux,
	}

	return server
}

func (s *Server) SetEventHandler(h ServerHandler) {
	s.eventHandler = h
}

func (s *Server) Run() {
	defer func() {
		err := recover()
		if err != nil {
			log.Errorf("api server panic, error: %v", err)
		}
	}()

	log.Infof("listening %s", s.http.Addr)
	err := s.http.ListenAndServe()
	if err != nil {
		log.Warningf("api server error: %s", err)
	}
}

func (s *Server) Close() {
	s.http.Close()
}

func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		if r.URL.Path == "/status" || r.URL.Path == "/status/" {
			s.handleGetStatus(w, r)
		} else {
			log.Warningf("%s %s %s %d %s", r.RemoteAddr, r.Method, r.URL.Path, http.StatusNotFound, http.StatusText(http.StatusNotFound))
		}
	default:
		log.Warningf("%s %s %s %d %s", r.RemoteAddr, r.Method, r.URL.Path, http.StatusNotFound, http.StatusText(http.StatusNotFound))
		http.NotFound(w, r)
	}
}

func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) {
	rsp := s.eventHandler.OnStatusReq()
	//w.Header().Add("Access-Control-Allow-Origin", "*")
	//w.Header().Add("Content-Type", "application/json")
	//w.WriteHeader(http.StatusOK)
	//log.Debugf("%s %s %s %d %s %s", r.RemoteAddr, r.Method, r.URL.Path, http.StatusOK, http.StatusText(http.StatusOK), status)

	if len(rsp.Meta.BackendMap) == 0 {
		log.Warningf("%s %s %s %d %s. cause by len(rsp.Meta.BackendMap) == 0",
			r.RemoteAddr, r.Method, r.URL.Path, http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable))
		s.sendResponse(w, r, http.StatusServiceUnavailable, rsp)

		return
	}

	connectCount := int16(0)
	for _, backend := range rsp.Meta.BackendMap {
		if backend.Connected {
			connectCount++
		}
	}

	if connectCount == 0 {
		log.Warningf("%s %s %s %d %s. cause by connectCount == 0",
			r.RemoteAddr, r.Method, r.URL.Path, http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable))
		s.sendResponse(w, r, http.StatusServiceUnavailable, rsp)
		return
	}

	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleRtc(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodPost:
		s.handleRtcPost(w, r)
	default:
		log.Warningf("%s %s %s %d %s", r.RemoteAddr, r.Method, r.URL.Path, http.StatusNotFound, http.StatusText(http.StatusNotFound))
		http.NotFound(w, r)
	}
}

func (s *Server) handleRtcPost(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Warningf("could not read request: %s", err)
		s.handleRequestError(w, r, "io error")
		return
	}

	log.Debugf("%s request %s %s %s", r.RemoteAddr, r.Method, r.URL.String(), body)

	req, err := ParseRequest(body)
	if err != nil {
		log.Warningf("could not decode request: %s", err)
		s.handleRequestError(w, r, "json error")
		return
	}

	// log.Infof("receive request [%s:%s]", req.TsxId, req.Op)

	switch req.Op {
	case "initC":
		s.handleCall(w, r, req)
	case "ansC":
		s.handleAnswer(w, r, req)
	case "termC":
		s.handleTerminate(w, r, req)
	case "tcklC":
		s.handleCandidate(w, r, req)
	case "subC":
		s.handleSubscribe(w, r, req)
	case "usubC":
		s.handleUnSubscribe(w, r, req)
	case "pubR":
		s.handlePubRemote(w, r, req)
	case "upubR":
		s.handleUnPubRemote(w, r, req)
	case "boot":
		s.handleBoot(w, r, req)
	case "recN":
		s.handleRecN(w, r, req)
	case "recD":
		s.handleRecD(w, r, req)
	case "recB":
		s.handleRecB(w, r, req)
	case "recE":
		s.handleRecE(w, r, req)
	case "rtcUpd":
		s.handleRtcUpd(w, r, req)
	default:
		s.handleRequestError(w, r, "could not handle [%s:%s]", req.TsxId, req.Op)
	}
}

func (s *Server) handleRequestError(w http.ResponseWriter, r *http.Request, format string, args ...interface{}) {
	//debug.PrintStack()
	rsp := &Response{
		Op:      "rsp",
		Status:  http.StatusBadRequest,
		Message: fmt.Sprintf(format, args...),
	}
	s.sendResponse(w, r, http.StatusBadRequest, rsp)
}

func (s *Server) handleCall(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnCallReq(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleAnswer(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnAnswerReq(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleTerminate(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnTerminateReq(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleCandidate(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnCandidateReq(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleSubscribe(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnSubscribeReq(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleUnSubscribe(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnUnSubscribeReq(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handlePubRemote(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnPubRemote(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleUnPubRemote(w http.ResponseWriter, r *http.Request,
	req *Request) {
	rsp := s.eventHandler.OnUnPubRemote(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleBoot(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnBootReq(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleRecN(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnNewScene(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}
func (s *Server) handleRecD(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnDelScene(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}
func (s *Server) handleRecB(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnNewSegment_2(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}
func (s *Server) handleRecE(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnDelSegment_2(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) handleRtcUpd(w http.ResponseWriter, r *http.Request, req *Request) {
	rsp := s.eventHandler.OnRtcUpd(req)
	s.sendResponse(w, r, http.StatusOK, rsp)
}

func (s *Server) sendResponse(w http.ResponseWriter, r *http.Request, status int, rsp *Response) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(status)
	if rsp != nil {
		//buf, err := json.MarshalIndent(rsp, "", "  ")
		buf, err := json.Marshal(rsp)

		if err != nil {
			log.Panicf("could not encode api response: %s", err)
		}
		w.Write(buf)
		log.Debugf("%s response %s %s %d %s %s", r.RemoteAddr, r.Method, r.URL.Path, status, http.StatusText(status), buf)
	} else {
		log.Debugf("%s response %s %s %d %s", r.RemoteAddr, r.Method, r.URL.Path, status, http.StatusText(status))
	}
}
